﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.statements;
using xplang.run;
using xplang.contexts;
using xplang.expression;
using xplang.rtl;
using xplang.errors;

namespace xplang.abstractions
{
  public abstract class ClassMethod
  {
    public ClassMethod(ClassDecl declaringClass, Modifier modifier, ClassRef returnType, String name, Variable[] parameters)
    {
      this.modifier = modifier;
      this.declaringClass = declaringClass;
      this.name = name;
      this.parameters = parameters;
      if (returnType == null) // void
        this.result = null;
      else
        this.result = new Variable(Modifier.None, "return", returnType, null);
    }

    public readonly ClassDecl declaringClass;
    public readonly Modifier modifier;
    public readonly String name;
    public readonly Variable result;
    public readonly Variable[] parameters;
    public Dictionary<ClassDecl, MethodImplementation> implementations = new Dictionary<ClassDecl, MethodImplementation>();

    public readonly List<Variable> locals = new List<Variable>();

    public int findVariable(String name, ref Variable variable)
    {
      int np = parameters.Length;
      for (int i = 0; i < np; i++)
      {
        variable = parameters[i];
        if (String.CompareOrdinal(variable.name, name) == 0)
          return i + 1;
      }
      int nl = locals.Count;
      for (int i = 0; i < nl; i++)
      {
        variable = locals[i];
        if (String.CompareOrdinal(variable.name, name) == 0)
          return np + i + 1;
      }
      return -1;
    }

    public Value[] makeHeap(Value _this, Value[] paramValues)
    {
      int np = parameters.Length;
      Value[] heap = new Value[1 + np + locals.Count + 1]; // this + params + locals + return
      heap[0] = _this;
      if (paramValues != null)
        for (int i = 0; i < np; i++)
        {
          if (parameters[i].byRef())
            heap[i + 1] = paramValues[i];
          else
            heap[i + 1] = paramValues[i].clone();
        }
      return heap;
    }

    public Value invokeIt(Value[] heap, ref FlowState state)
    {
      int np = 1 + parameters.Length;
      int nl = locals.Count;
      for (int i = 0; i < nl; i++)
      {
        heap[i + np] = locals[i].init.doIt(heap, ref state);
        if (state != FlowState.Normal)
          return null;
      }
      MethodImplementation imp = heap[0].type.findMethodImplementation(this);
      return imp.invokeIt(heap, ref state);
    }

    public virtual bool supportTheseParameters(ClassRef[] paramTypes, ClassRef[] mappings)
    {
      int n = paramTypes.Length;
      if (n > parameters.Length)
        return false;
      for (int i = 0; i < n; i++)
      {
        ClassRef pTry = paramTypes[i];
        ClassRef pDef = mappings[i];//        parameters[i].type.getGenericMappings();
        if (pDef.supports(pTry, mappings))
          return true;
      }
      return true;
    }

    public void registerImplementation(ClassDecl implementationClass, StatementBlock code)
    {
      implementations[implementationClass] = new CompiledMethodImplementation(code);
    }

    public void registerImplementation(ClassDecl implementationClass, NativeClassMethodInvocation implementation)
    {
      implementations[implementationClass] = new NativeMethodImplementation(implementation);
    }
  }

  public class NativeClassMethod : ClassMethod
  {
    public NativeClassMethod(ClassDecl declaringClass, Modifier modifier, ClassRef returnType, String name, Variable[] parameters)
      : base(declaringClass, modifier, returnType, name, parameters)
    {
    }
  }

  public class CompiledClassMethod : ClassMethod
  {
    public CompiledClassMethod(ClassDecl declaringClass, Modifier modifier, ClassRef returnType, String name, Variable[] parameters)
      : base(declaringClass, modifier, returnType, name, parameters)
    {
    }
  }

  public abstract class MethodImplementation
  {
    public abstract Value invokeIt(Value[] heap, ref FlowState state);
  }

  public class CompiledMethodImplementation : MethodImplementation
  {
    public CompiledMethodImplementation(StatementBlock code)
    {
      this.code = code;
    }
    public readonly StatementBlock code;
    public override Value invokeIt(Value[] heap, ref FlowState state)
    {
      code.doIt(heap, ref state);
      if (state == FlowState.ExitFromMethod)
        state = FlowState.Normal;
      return heap[heap.Length - 1];
    }
  }

  public delegate Value NativeClassMethodInvocation(Value[] heap, ref FlowState state);

  public class NativeMethodImplementation : MethodImplementation
  {
    public NativeMethodImplementation(NativeClassMethodInvocation invocation)
    {
      this.invocation = invocation;
    }

    public readonly NativeClassMethodInvocation invocation;

    public override Value invokeIt(Value[] heap, ref FlowState state)
    {
      return invocation(heap, ref state);
    }
  }
}
